SPB Git
2.6 KB · 32 lines tsx
1import type { Metadata } from "next";2import Link from "next/link";3import { notFound } from "next/navigation";4import { getCategory, searchTools, categoryTree } from "@/lib/public/queries";5import { parseFilters, type SP } from "@/lib/public/params";6import { Breadcrumbs } from "@/components/breadcrumbs";7import { ToolCard } from "@/components/tool-card";8import { Pagination } from "@/components/pagination";9export const revalidate = 600;10export async function generateStaticParams() { try { const t = await categoryTree(); return t.flatMap((c) => [{ slug: c.slug }, ...c.children.map((x) => ({ slug: x.slug }))]); } catch { return []; } }11export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {12  const c = await getCategory((await params).slug); if (!c) return { title: "Not found" };13  return { title: `${c.name} tools`, description: c.description, alternates: { canonical: `/categories/${c.slug}` } };14}15export default async function CategoryPage({ params, searchParams }: { params: Promise<{ slug: string }>; searchParams: Promise<SP> }) {16  const { slug } = await params; const c = await getCategory(slug); if (!c) notFound();17  const f = parseFilters(await searchParams);18  const res = await searchTools({ ...f, category: [slug], sort: f.sort ?? "quality" });19  return (20    <div className="mx-auto max-w-7xl px-4 py-8 sm:px-6">21      <Breadcrumbs items={[{ href: "/categories", label: "Categories" }, ...(c.parent ? [{ href: `/categories/${c.parent.slug}`, label: c.parent.name }] : []), { label: c.name }]} />22      <h1 className="display h1 mt-3">{c.name}</h1>23      <p className="mt-2 max-w-3xl text-fg-2">{c.description}</p>24      {c.children.length > 0 && <ul className="mt-5 flex flex-wrap gap-2">{c.children.map((ch) => <li key={ch.slug}><Link href={`/categories/${ch.slug}`} className="chip hover:border-accent/40 hover:text-accent">{ch.name} <span className="text-fg-3">{ch.count}</span></Link></li>)}</ul>}25      <div className="mt-8 flex items-center justify-between"><p className="text-sm text-fg-2">{res.total} tools</p><Link href={`/tools?category=${slug}`} className="text-sm text-accent hover:underline" rel="nofollow">Filter in directory →</Link></div>26      <h2 className="sr-only">Tools in this category</h2>27      {res.items.length ? <div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">{res.items.map((t) => <ToolCard key={t.id} t={t} />)}</div> : <p className="card mt-4 p-8 text-center text-fg-2">No published tools in this category yet.</p>}28      <Pagination page={res.page} pages={res.pages} makeHref={(p) => `/categories/${slug}?page=${p}`} />29    </div>30  );31}32
Files · main
Menu

Theme